#region Copyright

#endregion
#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Web.Caching;
using TamoCRM.Core.Caching;
using TamoCRM.Core.Collections.Internal;
using TamoCRM.Core.Common.Utilities;
using TamoCRM.Core.Instrumentation;


#endregion

namespace TamorCRM.Core.Caching
{
    /// -----------------------------------------------------------------------------
    /// <summary>
    /// The DataCache class is a facade class for the CachingProvider Instance's
    /// </summary>
    /// <history>
    ///     
    /// </history>
    /// -----------------------------------------------------------------------------
    public class DataCache
    {
        private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof (DataCache));
        
        private static string _CachePersistenceEnabled = "";

        private static readonly ReaderWriterLock dictionaryLock = new ReaderWriterLock();
        private static readonly Dictionary<string, object> lockDictionary = new Dictionary<string, object>();

        private static readonly SharedDictionary<string, Object> dictionaryCache = new SharedDictionary<string, Object>();

        public static bool CachePersistenceEnabled
        {
            get
            {
                if (string.IsNullOrEmpty(_CachePersistenceEnabled))
                {
                    _CachePersistenceEnabled = Config.GetSetting("EnableCachePersistence") ?? "false";
                }
                return bool.Parse(_CachePersistenceEnabled);
            }
        }

        private static string GetTamoCRMCacheKey(string CacheKey)
        {
            return CachingProvider.GetCacheKey(CacheKey);
        }

        private static string CleanCacheKey(string CacheKey)
        {
            return CachingProvider.CleanCacheKey(CacheKey);
        }

        internal static void ItemRemovedCallback(string key, object value, CacheItemRemovedReason removedReason)
        {
            //if the item was removed from the cache, log the key and reason to the event log
            try
            {
                Logger.Info(string.Format("Cached item {0} was removed", key));
                
            }
            catch (Exception exc)
            {
                //Swallow exception            
                Logger.Error(exc);
            }
        }

        public static void ClearCache()
        {
            CachingProvider.Instance().Clear("Prefix", "TamoCRM_");
            using (ISharedCollectionLock writeLock = dictionaryCache.GetWriteLock())
            {
                dictionaryCache.Clear();
            }
            Logger.Info("Refresh cache");
            
        }

        public static void ClearCache(string cachePrefix)
        {
            CachingProvider.Instance().Clear("Prefix", GetTamoCRMCacheKey(cachePrefix));
        }

        private static object GetCachedDataFromRuntimeCache(CacheItemArgs cacheItemArgs, CacheItemExpiredCallback cacheItemExpired)
        {
            object objObject = GetCache(cacheItemArgs.CacheKey);

            // if item is not cached
            if (objObject == null)
            {
                //Get Unique Lock for cacheKey
                object @lock = GetUniqueLockObject(cacheItemArgs.CacheKey);

                // prevent other threads from entering this block while we regenerate the cache
                lock (@lock)
                {
                    // try to retrieve object from the cache again (in case another thread loaded the object since we first checked)
                    objObject = GetCache(cacheItemArgs.CacheKey);

                    // if object was still not retrieved

                    if (objObject == null)
                    {
                        // get object from data source using delegate
                        try
                        {
                            objObject = cacheItemExpired(cacheItemArgs);
                        }
                        catch (Exception ex)
                        {
                            objObject = null;
                            //Exceptions.LogException(ex);
                        }

                        // set cache timeout
                        int timeOut = cacheItemArgs.CacheTimeOut * Convert.ToInt32(1000000/*Host.PerformanceSetting*/);

                        // if we retrieved a valid object and we are using caching
                        if (objObject != null && timeOut > 0)
                        {
                            // save the object in the cache
                            SetCache(cacheItemArgs.CacheKey,
                                     objObject,
                                     cacheItemArgs.CacheDependency,
                                     Cache.NoAbsoluteExpiration,
                                     TimeSpan.FromMinutes(timeOut),
                                     cacheItemArgs.CachePriority,
                                     cacheItemArgs.CacheCallback);

                            // check if the item was actually saved in the cache

                            if (GetCache(cacheItemArgs.CacheKey) == null)
                            {
                                Logger.Info(string.Format("Item {0} was not saved in the cache", cacheItemArgs.CacheKey));                                
                            }
                        }

                        //This thread won so remove unique Lock from collection
                        RemoveUniqueLockObject(cacheItemArgs.CacheKey);
                    }
                }
            }

            return objObject;
        }

        private static object GetCachedDataFromDictionary(CacheItemArgs cacheItemArgs, CacheItemExpiredCallback cacheItemExpired)
        {
            object cachedObject;

            bool isFound;
            using (ISharedCollectionLock readLock = dictionaryCache.GetReadLock())
            {
                isFound = dictionaryCache.TryGetValue(cacheItemArgs.CacheKey, out cachedObject);
            }

            if (!isFound)
            {
                // get object from data source using delegate
                try
                {
                    cachedObject = cacheItemExpired != null ? cacheItemExpired(cacheItemArgs) : null;
                }
                catch (Exception ex)
                {
                    cachedObject = null;
                    Logger.Error(ex.Message);
                }

                using (ISharedCollectionLock writeLock = dictionaryCache.GetWriteLock())
                {
                    if (!dictionaryCache.ContainsKey(cacheItemArgs.CacheKey))
                    {
                        if (cachedObject != null)
                        {
                            dictionaryCache[cacheItemArgs.CacheKey] = cachedObject;
                        }
                    }
                }
            }

            return cachedObject;
        }

        public static TObject GetCachedData<TObject>(CacheItemArgs cacheItemArgs, CacheItemExpiredCallback cacheItemExpired)
        {
            // declare local object and try and retrieve item from the cache
            return GetCachedData<TObject>(cacheItemArgs, cacheItemExpired, false);
        }

        internal static TObject GetCachedData<TObject>(CacheItemArgs cacheItemArgs, CacheItemExpiredCallback cacheItemExpired, bool storeInDictionary)
        {
            object objObject = storeInDictionary
                                   ? GetCachedDataFromDictionary(cacheItemArgs, cacheItemExpired)
                                   : GetCachedDataFromRuntimeCache(cacheItemArgs, cacheItemExpired);

            // return the object
            if (objObject == null)
            {
                return default(TObject);
            }
            return (TObject)objObject;
        }

        private static object GetUniqueLockObject(string key)
        {
            object @lock = null;
            dictionaryLock.AcquireReaderLock(new TimeSpan(0, 0, 5));
            try
            {
                //Try to get lock Object (for key) from Dictionary
                if (lockDictionary.ContainsKey(key))
                {
                    @lock = lockDictionary[key];
                }
            }
            finally
            {
                dictionaryLock.ReleaseReaderLock();
            }
            if (@lock == null)
            {
                dictionaryLock.AcquireWriterLock(new TimeSpan(0, 0, 5));
                try
                {
                    //Double check dictionary
                    if (!lockDictionary.ContainsKey(key))
                    {
                        //Create new lock
                        lockDictionary[key] = new object();
                    }
                    //Retrieve lock
                    @lock = lockDictionary[key];
                }
                finally
                {
                    dictionaryLock.ReleaseWriterLock();
                }
            }
            return @lock;
        }

        private static void RemoveUniqueLockObject(string key)
        {
            dictionaryLock.AcquireWriterLock(new TimeSpan(0, 0, 5));
            try
            {
                //check dictionary
                if (lockDictionary.ContainsKey(key))
                {
                    //Remove lock
                    lockDictionary.Remove(key);
                }
            }
            finally
            {
                dictionaryLock.ReleaseWriterLock();
            }
        }

        public static TObject GetCache<TObject>(string CacheKey)
        {
            object objObject = GetCache(CacheKey);
            if (objObject == null)
            {
                return default(TObject);
            }
            return (TObject)objObject;
        }

        public static object GetCache(string CacheKey)
        {
            return CachingProvider.Instance().GetItem(GetTamoCRMCacheKey(CacheKey));
        }

        public static void RemoveCache(string CacheKey)
        {
            CachingProvider.Instance().Remove(GetTamoCRMCacheKey(CacheKey));
        }

        public static void RemoveFromPrivateDictionary(string CacheKey)
        {
            using (ISharedCollectionLock writeLock = dictionaryCache.GetWriteLock())
            {
                dictionaryCache.Remove(CleanCacheKey(CacheKey));
            }
        }

        public static void SetCache(string CacheKey, object objObject)
        {
            TamoCRMCacheDependency objDependency = null;
            SetCache(CacheKey, objObject, objDependency, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
        }

        public static void SetCache(string CacheKey, object objObject, TamoCRMCacheDependency objDependency)
        {
            SetCache(CacheKey, objObject, objDependency, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
        }

        public static void SetCache(string CacheKey, object objObject, DateTime AbsoluteExpiration)
        {
            TamoCRMCacheDependency objDependency = null;
            SetCache(CacheKey, objObject, objDependency, AbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
        }

        public static void SetCache(string CacheKey, object objObject, TimeSpan SlidingExpiration)
        {
            TamoCRMCacheDependency objDependency = null;
            SetCache(CacheKey, objObject, objDependency, Cache.NoAbsoluteExpiration, SlidingExpiration, CacheItemPriority.Normal, null);
        }

        public static void SetCache(string CacheKey, object objObject, TamoCRMCacheDependency objDependency, DateTime AbsoluteExpiration, TimeSpan SlidingExpiration)
        {
            SetCache(CacheKey, objObject, objDependency, AbsoluteExpiration, SlidingExpiration, CacheItemPriority.Normal, null);
        }

        public static void SetCache(string CacheKey, object objObject, TamoCRMCacheDependency objDependency, DateTime AbsoluteExpiration, TimeSpan SlidingExpiration, CacheItemPriority Priority,
                                    CacheItemRemovedCallback OnRemoveCallback)
        {
            if (objObject != null)
            {
                //if no OnRemoveCallback value is specified, use the default method
                if (OnRemoveCallback == null)
                {
                    OnRemoveCallback = ItemRemovedCallback;
                }
                CachingProvider.Instance().Insert(GetTamoCRMCacheKey(CacheKey), objObject, objDependency, AbsoluteExpiration, SlidingExpiration, Priority, OnRemoveCallback);
            }
        }
        
    }
}